package com.sunny.springboot2.core.interceptor;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.sunny.springboot2.core.annotation.CacheLock;
import com.sunny.springboot2.core.annotation.LocalLock;
import com.sunny.springboot2.core.config.RedisLockHelper;
import com.sunny.springboot2.core.lock.CacheKeyGenerator;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 本地锁、redis分布式锁 AOP
 * @author sunny
 * @date 2018/11/27
 */
@Aspect
@Configuration
public class LockMethodInterceptor {

    private final RedisLockHelper redisLockHelper;
    private final CacheKeyGenerator cacheKeyGenerator;

    @Autowired
    public LockMethodInterceptor(RedisLockHelper redisLockHelper, CacheKeyGenerator cacheKeyGenerator) {
        this.redisLockHelper = redisLockHelper;
        this.cacheKeyGenerator = cacheKeyGenerator;
    }

    private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
            //最大缓存100个
            .maximumSize(100)
            //设置写缓存后， 5秒后过期
            .expireAfterWrite(5, TimeUnit.SECONDS)
            .build();

    /**
     * redis分布式锁
     *
     * @param joinPoint
     * @author sunny
     * @date 2018/11/29
     * @return java.lang.Object
     */
    @Around("execution(public * *(..)) && @annotation(com.sunny.springboot2.core.annotation.CacheLock)")
    public Object redisInterceptor(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        CacheLock lockAnnotation = method.getAnnotation(CacheLock.class);
        if (StringUtils.isBlank(lockAnnotation.prefix())) {
            throw new RuntimeException("lock key can not be null...");
        }

        final String lockKey = cacheKeyGenerator.getLockKey(joinPoint);
        String value = UUID.randomUUID().toString().replace("-", "");
        try {
            //假设上锁成功，但是设置过期时间失效，以后拿到的都是false
            final boolean success = redisLockHelper.lock(lockKey, value, lockAnnotation.expire(), lockAnnotation.timeUnit());
            if (!success) {
                throw new RuntimeException("重复提交！");
            }

            try {
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                throw new RuntimeException("系统异常！");
            }
        } finally {
            //TODO 演示的情况下，注释该代码，实际应该释放锁
//            redisLockHelper.unLock(lockKey, value);
        }
    }
    /**
     * 本地锁
     *
     * @param joinPoint
     * @author sunny
     * @date 2018/11/29
     * @return java.lang.Object
     */
    @Around("execution(public * *(..)) && @annotation(com.sunny.springboot2.core.annotation.LocalLock)")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LocalLock localLock = method.getAnnotation(LocalLock.class);
        String key = getKey(localLock.key(), joinPoint.getArgs());

        if (StringUtils.isNotBlank(key)) {
            if (CACHES.getIfPresent(key) != null) {
                throw new RuntimeException("请勿重复提交！");
            }

            //第一次提交，将key添加到缓存
            CACHES.put(key, key);
        }

        try {
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw new RuntimeException("系统异常！");
        }finally {
            // TODO 为了演示效果,这里就不调用 CACHES.invalidate(key); 代码了
        }
    }

    /**
     * key 的生成策略,如果想灵活可以写成接口与实现类的方式
     *
     * @param keyExpress 表达式
     * @param args 参数
     * @author sunny
     * @date 2018/11/27
     * @return java.lang.String
     */
    private String getKey(String keyExpress, Object[] args) {
        for (int i = 0; i < args.length; i++) {
            keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());
        }
        return keyExpress;
    }


}
